using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;

namespace Woz
{
    /// <summary>
    /// defines a propagate last change form that steps the user through
    /// the process of propagating the last change to multiple screens
    /// </summary>
    public partial class PLCForm : Form
    {
        /// <summary>
        /// creates an instance of the propagate last change form
        /// </summary>
        public PLCForm()
        {
            // initialize the components of this form
            this.InitializeComponent();

            // hide it in the task bar
            this.ShowInTaskbar = false;

            // make the form double buffered
            this.DoubleBuffered = true;

            // initialize the list of valid screens
            this.mValidScreens = new Dictionary<Woz.Action, List<Woz.Screen>>();
            
            // initialize the list of selected screens
            this.mSelectedScreens = new List<Woz.Screen>();

            // initialize the list of available screens
            this.mAvailableScreens = new List<Woz.Screen>();

            // initialize the list of changes
            this.mChanges = new List<Woz.Action>();

            // initialize the temp panel
            this.mTempPanel = new Panel();
            this.mTempPanel.Width = 13;

            // initialize the preview bitmap
            this.mPreview = new Bitmap(this.picPreview.Width, this.picPreview.Height);

            // set the image property of the preview picturebox
            this.picPreview.Image = this.mPreview;

            // clear the last selected
            this.mLastSelected = -1;

            // plc is not successfully initialized yet
            this.mSuccessfullyInitialized = false;
        }

        /// <summary>
        /// gets the screens to propagate the change to
        /// </summary>
        public Woz.Screen[] Screens
        {
            get
            {
                return this.mSelectedScreens.ToArray();
            }
        }

        /// <summary>
        /// gets the changes to propagate
        /// </summary>
        public Woz.Action[] Changes
        {
            get
            {
                return this.mChanges.ToArray();
            }
        }

        /// <summary>
        /// gets whether plc is successfully initialized
        /// </summary>
        public bool SuccessfullyInitialized
        {
            get
            {
                return this.mSuccessfullyInitialized;
            }
        }

        /// <summary>
        /// initializes the plc form before displaying it
        /// </summary>
        /// <returns>true if successful or false otherwise</returns>
        public bool Initialize()
        {
            // indicates success
            bool success = true;

            // the percent loaded so far
            float percentLoaded = 0;

            // clear the valid screens
            this.mValidScreens.Clear();

            // clear the selected screens
            this.mSelectedScreens.Clear();

            // clear the available screens
            this.mAvailableScreens.Clear();

            // get a list of all the actions
            Woz.Action[] allActions = Woz.ActionManager.Instance.GetActions();

            // initialize the list of available actions
            List<Woz.Action> availableActions = new List<Woz.Action>();

            // get the list of all the screens
            List<Woz.Screen> allScreens = Woz.ScreenManager.Instance.Screens;

            // get the current screen
            Woz.Screen currentScreen = Woz.ScreenManager.Instance.SelectedScreen;

            // update the progress bars
            percentLoaded += 5;
            Woz.ResourceManager.Instance.ProgressUpdateMethod((int)percentLoaded);

            // clear the changes list and last selected
            this.listChanges.Items.Clear();
            this.mLastSelected = -1;

            // go through the actions and get the actions that happened on the current screen
            for (int i = 0; i < allActions.Length; i++)
            {
                if (allActions[i].Screen == currentScreen)
                {
                    if (allActions[i].Propagatable)
                    {
                        // add it to the changes list
                        this.listChanges.Items.Add(allActions[i]);

                        // add it to the available actions list
                        availableActions.Add(allActions[i]);

                        // initialize the valid screens array
                        this.mValidScreens.Add(allActions[i], new List<Woz.Screen>());
                    }
                }
                else
                {
                    break;
                }
            }

            // update the progress bars
            percentLoaded += 5;
            Woz.ResourceManager.Instance.ProgressUpdateMethod((int)percentLoaded);

            // clear the graphics
            using (Graphics graphics = Graphics.FromImage(this.mPreview))
            {
                graphics.Clear(Woz.StaticVariables.Instance.InitialBackgroundColor);
            }

            // make sure there are more than one items
            success = (allActions.Length != 0);

            // check to see if we were sucessful before continuing
            if (success)
            {
                // get a list of all the screens the actions can be applied to
                float incrementAmount = 90.0f / (float)(allActions.Length + 1);
                foreach (Woz.Action action in availableActions)
                {
                    // update the progress bars
                    percentLoaded += incrementAmount;
                    Woz.ResourceManager.Instance.ProgressUpdateMethod((int)percentLoaded);

                    // go through all the screens and see if the action can be applied to them
                    foreach (Woz.Screen screen in allScreens)
                    {
                        // add it to the valid screens if it can be applied to this screen
                        if ((screen != currentScreen) && action.CanApplyTo(screen))
                        {
                            // add it to the valid screens of this action
                            this.mValidScreens[action].Add(screen);

                            // make sure we havent already added this screen
                            if (!this.mAvailableScreens.Contains(screen))
                            {
                                // add it to the available screens
                                this.mAvailableScreens.Add(screen);

                                // also add it to the selected screens
                                this.mSelectedScreens.Add(screen);

                                // initialize the screen thumbnail
                                screen.PropagateThumbnail.Margin = new Padding(3, 0, 3, 0);
                                screen.PropagateThumbnail.Selected = true;
                                screen.PropagateThumbnail.HighlightOnMouseOver = false;

                                // first remove any pending events on this thumbnail and add a new one
                                screen.PropagateThumbnail.Click -= new EventHandler(this.cPropagateThumbnailClicked);
                                screen.PropagateThumbnail.Click += new EventHandler(this.cPropagateThumbnailClicked);
                            }
                        }
                    }
                }

                // update the progress bars
                percentLoaded += incrementAmount;
                Woz.ResourceManager.Instance.ProgressUpdateMethod((int)percentLoaded);

                // select the screens
                if (availableActions.Count > 0)
                {
                    this.listChanges.SelectedItem = availableActions[0];
                }
                else
                {
                    success = false;
                }
            }
            else
            {
                // display some sort of error dialog
                MessageBox.Show(this, 
                    "You have not made any recent propagateable changes on this screen.", 
                    Application.ProductName,
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);
            }

            // return the success message
            return (this.mSuccessfullyInitialized = success);
        }

        /// <summary>
        /// populates the screens
        /// </summary>
        private void PopulateScreens()
        {
            // clear the screens panel and screens list
            this.mSelectedScreens.Clear();

            // add all of the available screens to the selected screens
            this.mSelectedScreens.AddRange(this.mAvailableScreens);

            // now go through the list of valid screens and add them to the panel
            for (int i = 0; i <= this.mLastSelected; i++)
            {
                // get the action
                Woz.Action action = (Woz.Action)this.listChanges.Items[i];

                // go through all the available screens
                foreach (Woz.Screen screen in this.mAvailableScreens)
                {
                    // end early if there are no more selected screens
                    if (this.mSelectedScreens.Count < 1)
                    {
                        break;
                    }

                    // if the screen is not valid for this action
                    if (!this.mValidScreens[action].Contains(screen))
                    {
                        // remove it from the selected screens list
                        this.mSelectedScreens.Remove(screen);
                    }
                }

                // end early if there are no more selected screens
                if (this.mSelectedScreens.Count < 1)
                {
                    break;
                }
            }
            
            // now add each of the selected screens to the screens panel
            this.panelScreens.Controls.Clear();
            foreach (Woz.Screen screen in this.mSelectedScreens)
            {
                this.panelScreens.Controls.Add(screen.PropagateThumbnail);
            }

            // add a small temp panel to give it padding on the right side
            this.panelScreens.Controls.Add(this.mTempPanel);

            // enable or disable the ok button
            this.buttonOk.Enabled = (this.mSelectedScreens.Count > 0);
        }

        /// <summary>
        /// updates the preview image
        /// </summary>
        private void UpdatePreview()
        {
            // draw to the preview image
            using (Graphics graphics = Woz.InkSpaceConverter.Instance.InitializeInkGraphics(Graphics.FromImage(this.mPreview)))
            {
                // first set the smoothing mode and scale it proper
                graphics.SmoothingMode = SmoothingMode.AntiAlias;
                graphics.PageScale *= ((float)this.picPreview.Width / (float)Woz.StaticVariables.Instance.CanvasSize.Width);

                // clear the image to the background color
                graphics.Clear(Woz.StaticVariables.Instance.InitialBackgroundColor);
                
                // now draw the screen with the selected actions
                Woz.Action action = (Woz.Action)this.listChanges.SelectedItem;
                Woz.Screen screen = action.Screen.Clone("PLCScreen");

                // un apply all the dependant actions to the screen
                for (int i = this.listChanges.Items.Count - 1; i > this.mLastSelected; i--)
                {
                    action = (Woz.Action)this.listChanges.Items[i];
                    action.UnApplyTo(screen);
                }

                // draw the screen to the graphics
                screen.Draw(graphics);

                // clear the screen
                screen.Clear();
            }

            // refresh the preview picturebox
            this.picPreview.Refresh();
        }
        
        /// <summary>
        /// selects all of the changes that depend on the selected change
        /// </summary>
        /// <returns>true if the selection really changed</returns>
        private bool SelectDependingChanges()
        {
            // first get the selected index
            int selectedIndex = this.listChanges.SelectedIndex;
            
            // compare to last selected
            if (this.mLastSelected != selectedIndex)
            {
                // clear the list of actions
                this.mChanges.Clear();

                // select them all
                for (int i = 0; i < this.listChanges.Items.Count; i++)
                {
                    bool check = (i <= selectedIndex);
                    this.listChanges.SetItemChecked(i, check);
                    if (check)
                    {
                        this.mChanges.Add((Woz.Action)this.listChanges.Items[i]);
                    }
                }

                // update the last selected
                this.mLastSelected = selectedIndex;

                // indicate success
                return true;
            }

            // we did not select somethin new
            return false;
        }

        /// <summary>
        /// handles the visible changed event
        /// </summary>
        /// <param name="pEventArgs">the event args</param>
        protected override void OnVisibleChanged(EventArgs pEventArgs)
        {
            base.OnVisibleChanged(pEventArgs);
        }

        /// <summary>
        /// handles the ok button being clicked
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pEventArgs">the associated event args</param>
        private void cButtonOkClicked(object pSender, EventArgs pEventArgs)
        {
            this.DialogResult = DialogResult.OK;
            this.Close();
        }

        /// <summary>
        /// handles the cancel button being clicked
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pEventArgs">the associated event args</param>
        private void cButtonCancelClicked(object pSender, EventArgs pEventArgs)
        {
            this.DialogResult = DialogResult.Cancel;
            this.Close();
        }

        /// <summary>
        /// handles the selected index of the changes list changing
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pEventArgs">the associated event args</param>
        private void cListChangesSelectedIndexChanged(object pSender, EventArgs pEventArgs)
        {
            // block out calls to this function until all depending changes are selected
            if (this.listChanges.Tag == null)
            {
                this.listChanges.Tag = pSender;

                // select the depending changes
                if (this.SelectDependingChanges())
                {
                    // update the picture box
                    this.UpdatePreview();

                    // update the screens
                    this.PopulateScreens();
                }

                // now clear the tag
                this.listChanges.Tag = null;
            }
        }

        /// <summary>
        /// lets the user select a screen
        /// </summary>
        /// <param name="pSender">the sender of the event</param>
        /// <param name="pEventArgs">the associated event args</param>
        private void cPropagateThumbnailClicked(object pSender, EventArgs pEventArgs)
        {
            Woz.ScreenThumbnail thumb = (Woz.ScreenThumbnail)pSender;
            if (thumb.Selected)
            {
                this.mSelectedScreens.Remove(thumb.Screen);
                thumb.Selected = false;
            }
            else
            {
                this.mSelectedScreens.Add(thumb.Screen);
                thumb.Selected = true;
            }

            // enable or disable the ok button
            this.buttonOk.Enabled = (this.mSelectedScreens.Count > 0);
        }

        /// <summary>
        /// list of valid screens for each action
        /// </summary>
        private Dictionary<Woz.Action, List<Woz.Screen>> mValidScreens;

        /// <summary>
        /// list of selected screens
        /// </summary>
        private List<Woz.Screen> mSelectedScreens;

        /// <summary>
        /// list of available screens
        /// </summary>
        private List<Woz.Screen> mAvailableScreens;

        /// <summary>
        /// list of selected changes
        /// </summary>
        private List<Woz.Action> mChanges;

        /// <summary>
        /// temp panel to fix flow layout margin issues
        /// </summary>
        private Panel mTempPanel;

        /// <summary>
        /// the preview image
        /// </summary>
        private Bitmap mPreview;

        /// <summary>
        /// indicates whether plc is successfully initialized
        /// </summary>
        private bool mSuccessfullyInitialized;

        /// <summary>
        /// the last selected change
        /// </summary>
        private int mLastSelected;
    }
}